/*
 * This is GPL v2 licensed code. See the file LICENSE in the
 * source distribution, or www.gnu.org/copyleft/gpl.html, for
 * more information.
 *
 * Copyright (C) by Håkan Lindqvist 2006, 2007
 */

package PolicyAlgebra.Util;

import java.io.InputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;


/** 
 * This class loads an entire JAR archive and instantiates
 * any class that was requested, used by the plugin sub system.
 */
public class JarClassLoader extends ClassLoader {
	private String jarFileName;
	private HashMap<String,byte[]> jarContents;


	/** 
	 * Reads all the <b>class</b> contents from a JAR archive for later use. 
	 * 
	 * @param jarFile The JAR file to load.
	 * @throws ClassNotFoundException If something goes wrong.
	 */
	private void loadJarFile(JarFile jarFile) throws ClassNotFoundException {
		Enumeration<JarEntry> je = jarFile.entries();
		JarEntry entry = null;
		InputStream is = null;

		try {
			while (je.hasMoreElements()) {
				entry = je.nextElement();
				String entryName = entry.getName();

				/* Skip any directories and non class files */
				if (entry.isDirectory() || !entryName.endsWith(".class")) {
					continue;
				}

				/* Correct the class name (i.e. change "/" to "." and remove ".class" */
				String dirName = entryName.substring(0, entryName.length() - 6);
				String actualName = dirName.replace("/", ".");

				/* XXX : Is this a potential problem? */
				int size = (int) entry.getSize();
				byte[] b = new byte[size];

				is = jarFile.getInputStream(entry);

				if (is.read(b) == -1) {
					throw new Exception("Failed to read any data from the entry for the class "
							+ entryName + " in JAR file " + jarFileName);
				}

				jarContents.put(actualName, b);
			}
		} catch (Exception e) {
			/* This ugly catch is used to handle any error that may occur.
			 * Quick and dirty in other words.
			 */
			throw new ClassNotFoundException("Failed to find / load the wanted class data or any of its helper classes data from the JAR archive (" + jarFile.getName() + ")");
		}
	}


	/** 
	 * Constructor for the JAR class loader.
	 * 
	 * @param jarFileName The JAR archive to load classes from.
	 * @throws IOException Thrown if the class loader fails to open
	 *         the JAR archive or read any of its contents.
	 *         
	 */
	public JarClassLoader(String jarFileName) throws IOException {
		this.jarFileName = jarFileName;
		jarContents = new HashMap<String,byte[]>();

		/* Process the contents of the JAR archive for later use */
		try {
			loadJarFile(new JarFile(jarFileName));
		} catch (Exception e) {
			throw new IOException("Failed to read contents of the JAR archive.");
		}
	}


	/** 
	 * Finds and loads a class. 
	 * 
	 * @param name The class to load.
	 * @return The Class meta data for the specified class.
	 * @throws ClassNotFoundException If anything goes wrong.
	 */
	public Class findClass(String name) throws ClassNotFoundException {
		byte[] bytecode = jarContents.get(name);

		if (bytecode == null) {
			throw new ClassNotFoundException("The class '" 
					+ name + "'"
					+ " was not found in the archive ("
					+ jarFileName + ")");
		}

		// Free some memory in the hashtable, the Class will later be handled
		// as a part of the runtime system (i.e. well known), it is cached 
		// and can be found by the the loadClass method in the standard ClassLoader.
		jarContents.remove(name);

		return defineClass(name, bytecode, 0, bytecode.length);
	}
}
